/*
 * Copyright 2007 National Bioinformatics Network
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package dgbgui.gui;

import java.awt.geom.Point2D;
import java.util.LinkedHashSet;
import java.util.Observable;
import java.util.Set;

import dgbgui.gui.widgets.WidgetFactory;
import dgbgui.gui.widgets.WidgetStore;
import dgbgui.gui.widgets.visual.QueryWidget;
import dgbgui.modeldocument.ModelDocument;
import dgbgui.modeldocument.Query;
import dgbgui.modeldocument.QueryResult;
import dgbgui.modeldocument.ResultStore;
import dgbgui.modeldocument.primitives.Conceptron;
import dgbgui.modeldocument.primitives.ConceptronType;
import dgbgui.modeldocument.primitives.Graph;
import dgbgui.modeldocument.primitives.Primitive;
import dgbgui.selection.SelectionManager;
import edu.umd.cs.piccolo.PLayer;

public class DefaultCanvasScene extends GraphicsScene {
    
    
    public static final double PADDING = 20.0; 

    private DefaultCanvasScene() {
        m_instance = this;
    }
    
    public static DefaultCanvasScene getInstance() {
        if ( m_instance == null ) {
            m_instance = new DefaultCanvasScene();
        }
        return m_instance;
    }
    
    public void update(Observable source, Object data) {
        
        if ( source instanceof ModelDocument ) {
            assert ( data instanceof ModelDocument.ObservableData);
            ModelDocument.ObservableData canvasData = (ModelDocument.ObservableData)data;
            switch (canvasData.getType()) { 
            case QUERY_ADD : queryAdded(canvasData.getNode()); break;
            case QUERY_REMOVE : queryRemoved(canvasData.getNode()); break;
            case QUERY_REPLACE : queryReplaced(canvasData.getFromNode(),canvasData.getToNode()); break;
            case QUERY_RELABELLED : queryRelabelled(canvasData.getNode()); break;
            case CLIPBOARD_CHANGED : break; // Do nothing
            default: assert false;
            }
        }
        
        if ( source instanceof ResultStore ) {
            assert ( data instanceof ResultStore.ObservableData);
            ResultStore.ObservableData resultData = (ResultStore.ObservableData)data;
            switch (resultData.getType()) { 
            case QUERY_SUBMITTED: querySubmitted(resultData.getQueryString()); break;
            case RESULT_RECEIVED: resultReceived(resultData.getQueryString()); break;
            case RESULT_REMOVED: resultRemoved(resultData.getQueryString(),resultData.getQueryResult()); break;
            // Must add support for all events
            default: assert false;
            }
        }


        if ( source instanceof SelectionManager ) {
            assert ( data instanceof SelectionManager.ObservableData );
            SelectionManager.ObservableData selectionData = (SelectionManager.ObservableData)data;
            LinkedHashSet<Query> selectedQueryNodes = new LinkedHashSet<Query>();
            LinkedHashSet<Object> selectedResults = new LinkedHashSet<Object>();
            for ( Object obj : selectionData.getItems() ) {
                if ( obj instanceof Query ) {
                    selectedQueryNodes.add((Query)obj);
                }
                if ( ( obj instanceof ConceptronType ) ||
                     ( obj instanceof Conceptron) ||
                     ( obj instanceof Graph ) ) {
                    selectedResults.add(obj);
                }
            }
            if ( selectedQueryNodes.size() > 0 ) {
                switch (selectionData.getType()) {
                case ADD : queriesSelected(selectedQueryNodes); break;
                case REMOVE : queriesDeselected(selectedQueryNodes); break;
                }
            }
            if ( selectedResults.size() > 0 ) {
                switch (selectionData.getType()) {
                case ADD : highlightResult(selectedResults); break;
                case REMOVE : unhighlightResult(selectedResults); break;
                }
            }
        }
    }
    
    private void queryAdded(Query query) {
        
        // Get the new position before creating the widget, as it is added
        // to the scene upon creation
        Point2D newPosition = getInitialPosition();

        // A root query
        QueryWidget widget = WidgetFactory.createWidget(m_layer,query,true);

        // If the position state is present (not null), determine the initial 
        // position 
        if ( query.getState().getPosition() == null ) {
            query.getState().setPosition(newPosition);
        }

        widget.setOffset(query.getState().getPosition());

        // Notify the observers
        setChanged();
        notifyObservers();

    }
    
    private void queryRemoved(Query query) {
        // Remove the item from the scene
        WidgetStore.getInstance().remove(query);

        // Notify the observers
        setChanged();
        notifyObservers();
    }
    
    private void queryReplaced(Query fromNode, Query toNode) {
        queryRemoved(fromNode);
        queryAdded(toNode);
    }
    
    /**
     * Updates the representation of a query to indicate that
     * it has been submitted
     * @param queryString The query string which has been submitted
     */
    private void querySubmitted(String queryString) {
        for ( Query query : ModelDocument.getInstance().getQueriesWithQL(queryString)) {
            Set<QueryWidget> widgets = WidgetStore.getInstance().getWidgets(query);
            for ( QueryWidget widget : widgets ) {
                widget.updateResult();
            }
        }
        setChanged();
        notifyObservers();
    }
    
    /**
     * @param query The query that has been relabelled
     */
    private void queryRelabelled(Query query) {
        Set<QueryWidget> widgets = WidgetStore.getInstance().getWidgets(query);
        for ( QueryWidget widget : widgets ) {
            widget.updateTitleBar();
        }
        setChanged();
        notifyObservers();
    }
    
    /**
     * Updates the results representation from the result of the query
     * @param queryString the query string for which a result has been received
     */
    private void resultReceived(String queryString) {
        for ( Query query : ModelDocument.getInstance().getQueriesWithQL(queryString)) {
            Set<QueryWidget> widgets = WidgetStore.getInstance().getWidgets(query);
            for ( QueryWidget widget : widgets ) {
                widget.updateResult();
            }
        }
        setChanged();
        notifyObservers();

    }
    
    private void resultRemoved(String queryString, QueryResult result) {
        for ( Primitive primitive : result.getPrimitives() ) {
            WidgetStore.getInstance().remove(primitive);
        }
        setChanged();
        notifyObservers();
    }
    
    
    public void setDefinitionExpanded(QueryWidget widget, boolean expanded) {
        widget.updateDefinitionExpanded(expanded);
    }
    
    public void setResultExpanded(QueryWidget widget, boolean expanded) {
        widget.updateResultExpanded(expanded);
    }
    
    
    /**
     * Determine teh initial position of the PNode in the scene
     * @return The initial position
     */
    private Point2D getInitialPosition() {
        // Put the new node to the right of the scene
        return new Point2D.Double(m_layer.getFullBounds().getMaxX() + PADDING,PADDING);
    }
    
    /**
     * @return the layer node of the graphics scene
     */
    public PLayer getLayer() {
        assert ( m_layer instanceof PLayer );
        return (PLayer)m_layer;
    }

    
    

    

    static DefaultCanvasScene m_instance;

}
